/*
 * Copyright (c) 2007-2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2002,2004,2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Sep 14, 2000:
//  Fixed comments to preserve whitespaces and add a line break
//  when indenting. Reported by Gervase Markham <gerv@gerv.net>
// Sep 14, 2000:
//  Fixed serializer to report IO exception directly, instead at
//  the end of document processing.
//  Reported by Patrick Higgins <phiggins@transzap.com>
// Sep 13, 2000:
//   CR in character data will print as &#0D;
// Aug 25, 2000:
//   Fixed processing instruction printing inside element content
//   to not escape content. Reported by Mikael Staldal
//   <d96-mst@d.kth.se>
// Aug 25, 2000:
//   Added ability to omit comments.
//   Contributed by Anupam Bagchi <abagchi@jtcsv.com>
// Aug 26, 2000:
//   Fixed bug in newline handling when preserving spaces.
//   Contributed by Mike Dusseault <mdusseault@home.com>
// Aug 29, 2000:
//   Fixed state.unescaped not being set to false when
//   entering element state.
//   Reported by Lowell Vaughn <lvaughn@agillion.com>

package com.sun.org.apache.xml.internal.serialize;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Hashtable;
import java.util.Vector;

import com.sun.org.apache.xerces.internal.dom.DOMErrorImpl;
import com.sun.org.apache.xerces.internal.dom.DOMLocatorImpl;
import com.sun.org.apache.xerces.internal.dom.DOMMessageFormatter;
import com.sun.org.apache.xerces.internal.util.XMLChar;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.DOMError;
import org.w3c.dom.DOMErrorHandler;
import org.w3c.dom.Element;
import org.w3c.dom.Entity;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Notation;
import org.w3c.dom.ls.LSException;
import org.w3c.dom.ls.LSSerializerFilter;
import org.w3c.dom.traversal.NodeFilter;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.DocumentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;

/**
 * Base class for a serializer supporting both DOM and SAX pretty
 * serializing of XML/HTML/XHTML documents. Derives classes perform
 * the method-specific serializing, this class provides the common
 * serializing mechanisms.
 * <p>
 * The serializer must be initialized with the proper writer and
 * output format before it can be used by calling {@link #setOutputCharStream}
 * or {@link #setOutputByteStream} for the writer and {@link #setOutputFormat}
 * for the output format.
 * <p>
 * The serializer can be reused any number of times, but cannot
 * be used concurrently by two threads.
 * <p>
 * If an output stream is used, the encoding is taken from the
 * output format (defaults to <tt>UTF-8</tt>). If a writer is
 * used, make sure the writer uses the same encoding (if applies)
 * as specified in the output format.
 * <p>
 * The serializer supports both DOM and SAX. DOM serializing is done
 * by calling {@link #serialize(Document)} and SAX serializing is done by firing
 * SAX events and using the serializer as a document handler.
 * This also applies to derived class.
 * <p>
 * If an I/O exception occurs while serializing, the serializer
 * will not throw an exception directly, but only throw it
 * at the end of serializing (either DOM or SAX's {@link
 * org.xml.sax.DocumentHandler#endDocument}.
 * <p>
 * For elements that are not specified as whitespace preserving,
 * the serializer will potentially break long text lines at space
 * boundaries, indent lines, and serialize elements on separate
 * lines. Line terminators will be regarded as spaces, and
 * spaces at beginning of line will be stripped.
 * <p>
 * When indenting, the serializer is capable of detecting seemingly
 * element content, and serializing these elements indented on separate
 * lines. An element is serialized indented when it is the first or
 * last child of an element, or immediate following or preceding
 * another element.
 *
 *
 * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
 * @author <a href="mailto:rahul.srivastava@sun.com">Rahul Srivastava</a>
 * @author Elena Litani, IBM
 * @author Sunitha Reddy, Sun Microsystems
 * @see Serializer
 * @see LSSerializer
 */
public abstract class BaseMarkupSerializer implements ContentHandler, DocumentHandler, LexicalHandler, DTDHandler, DeclHandler, DOMSerializer, Serializer {

    // DOM L3 implementation
    protected short features = 0xFFFFFFFF;
    protected DOMErrorHandler fDOMErrorHandler;
    protected final DOMErrorImpl fDOMError = new DOMErrorImpl();
    protected LSSerializerFilter fDOMFilter;

    protected EncodingInfo _encodingInfo;

    /**
     * Holds array of all element states that have been entered.
     * The array is automatically resized. When leaving an element,
     * it's state is not removed but reused when later returning
     * to the same nesting level.
     */
    private ElementState[] _elementStates;

    /**
     * The index of the next state to place in the array,
     * or one plus the index of the current state. When zero,
     * we are in no state.
     */
    private int _elementStateCount;

    /**
     * Vector holding comments and PIs that come before the root
     * element (even after it), see {@link #serializePreRoot}.
     */
    private Vector _preRoot;

    /**
     * If the document has been started (header serialized), this
     * flag is set to true so it's not started twice.
     */
    protected boolean _started;

    /**
     * True if the serializer has been prepared. This flag is set
     * to false when the serializer is reset prior to using it,
     * and to true after it has been prepared for usage.
     */
    private boolean _prepared;

    /**
     * Association between namespace URIs (keys) and prefixes (values).
     * Accumulated here prior to starting an element and placing this
     * list in the element state.
     */
    protected Hashtable _prefixes;

    /**
     * The system identifier of the document type, if known.
     */
    protected String _docTypePublicId;

    /**
     * The system identifier of the document type, if known.
     */
    protected String _docTypeSystemId;

    /**
     * The output format associated with this serializer. This will never
     * be a null reference. If no format was passed to the constructor,
     * the default one for this document type will be used. The format
     * object is never changed by the serializer.
     */
    protected OutputFormat _format;

    /**
     * The printer used for printing text parts.
     */
    protected Printer _printer;

    /**
     * True if indenting printer.
     */
    protected boolean _indenting;

    /** Temporary buffer to store character data */
    protected final StringBuffer fStrBuffer = new StringBuffer(40);

    /**
     * The underlying writer.
     */
    private Writer _writer;

    /**
     * The output stream.
     */
    private OutputStream _output;

    /** Current node that is being processed  */
    protected Node fCurrentNode = null;

    //--------------------------------//
    // Constructor and initialization //
    //--------------------------------//

    /**
     * Protected constructor can only be used by derived class.
     * Must initialize the serializer before serializing any document,
     * by calling {@link #setOutputCharStream} or {@link #setOutputByteStream}
                 * first
     */
    protected BaseMarkupSerializer(OutputFormat format) {
        int i;

        _elementStates = new ElementState[10];
        for (i = 0; i < _elementStates.length; ++i)
            _elementStates[i] = new ElementState();
        _format = format;
    }

    public DocumentHandler asDocumentHandler() throws IOException {
        prepare();
        return this;
    }

    public ContentHandler asContentHandler() throws IOException {
        prepare();
        return this;
    }

    public DOMSerializer asDOMSerializer() throws IOException {
        prepare();
        return this;
    }

    public void setOutputByteStream(OutputStream output) {
        if (output == null) {
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "ArgumentIsNull", new Object[] { "output" });
            throw new NullPointerException(msg);
        }
        _output = output;
        _writer = null;
        reset();
    }

    public void setOutputCharStream(Writer writer) {
        if (writer == null) {
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "ArgumentIsNull", new Object[] { "writer" });
            throw new NullPointerException(msg);
        }
        _writer = writer;
        _output = null;
        reset();
    }

    public void setOutputFormat(OutputFormat format) {
        if (format == null) {
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "ArgumentIsNull", new Object[] { "format" });
            throw new NullPointerException(msg);
        }
        _format = format;
        reset();
    }

    public boolean reset() {
        if (_elementStateCount > 1) {
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "ResetInMiddle", null);
            throw new IllegalStateException(msg);
        }
        _prepared = false;
        fCurrentNode = null;
        fStrBuffer.setLength(0);
        return true;
    }

    protected void prepare() throws IOException {
        if (_prepared)
            return;

        if (_writer == null && _output == null) {
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "NoWriterSupplied", null);
            throw new IOException(msg);
        }
        // If the output stream has been set, use it to construct
        // the writer. It is possible that the serializer has been
        // reused with the same output stream and different encoding.

        _encodingInfo = _format.getEncodingInfo();

        if (_output != null) {
            _writer = _encodingInfo.getWriter(_output);
        }

        if (_format.getIndenting()) {
            _indenting = true;
            _printer = new IndentPrinter(_writer, _format);
        } else {
            _indenting = false;
            _printer = new Printer(_writer, _format);
        }

        ElementState state;

        _elementStateCount = 0;
        state = _elementStates[0];
        state.namespaceURI = null;
        state.localName = null;
        state.rawName = null;
        state.preserveSpace = _format.getPreserveSpace();
        state.empty = true;
        state.afterElement = false;
        state.afterComment = false;
        state.doCData = state.inCData = false;
        state.prefixes = null;

        _docTypePublicId = _format.getDoctypePublic();
        _docTypeSystemId = _format.getDoctypeSystem();
        _started = false;
        _prepared = true;
    }

    //----------------------------------//
    // DOM document serializing methods //
    //----------------------------------//

    /**
     * Serializes the DOM element using the previously specified
     * writer and output format. Throws an exception only if
     * an I/O exception occured while serializing.
     *
     * @param elem The element to serialize
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    public void serialize(Element elem) throws IOException {
        reset();
        prepare();
        serializeNode(elem);
        _printer.flush();
        if (_printer.getException() != null)
            throw _printer.getException();
    }

    /**
     * Serializes a node using the previously specified
     * writer and output format. Throws an exception only if
     * an I/O exception occured while serializing.
     *
     * @param node Node to serialize
     * @throws IOException An I/O exception occured while serializing
     */
    public void serialize(Node node) throws IOException {
        reset();
        prepare();
        serializeNode(node);
        //Print any PIs and Comments which appeared in 'node'
        serializePreRoot();
        _printer.flush();
        if (_printer.getException() != null)
            throw _printer.getException();
    }

    /**
     * Serializes the DOM document fragmnt using the previously specified
     * writer and output format. Throws an exception only if
     * an I/O exception occured while serializing.
     *
     * @param elem The element to serialize
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    public void serialize(DocumentFragment frag) throws IOException {
        reset();
        prepare();
        serializeNode(frag);
        _printer.flush();
        if (_printer.getException() != null)
            throw _printer.getException();
    }

    /**
     * Serializes the DOM document using the previously specified
     * writer and output format. Throws an exception only if
     * an I/O exception occured while serializing.
     *
     * @param doc The document to serialize
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    public void serialize(Document doc) throws IOException {
        reset();
        prepare();
        serializeNode(doc);
        serializePreRoot();
        _printer.flush();
        if (_printer.getException() != null)
            throw _printer.getException();
    }

    //------------------------------------------//
    // SAX document handler serializing methods //
    //------------------------------------------//

    public void startDocument() throws SAXException {
        try {
            prepare();
        } catch (IOException except) {
            throw new SAXException(except.toString());
        }
        // Nothing to do here. All the magic happens in startDocument(String)
    }

    public void characters(char[] chars, int start, int length) throws SAXException {
        ElementState state;

        try {
            state = content();

            // Check if text should be print as CDATA section or unescaped
            // based on elements listed in the output format (the element
            // state) or whether we are inside a CDATA section or entity.

            if (state.inCData || state.doCData) {
                int saveIndent;

                // Print a CDATA section. The text is not escaped, but ']]>'
                // appearing in the code must be identified and dealt with.
                // The contents of a text node is considered space preserving.
                if (!state.inCData) {
                    _printer.printText("<![CDATA[");
                    state.inCData = true;
                }
                saveIndent = _printer.getNextIndent();
                _printer.setNextIndent(0);
                char ch;
                final int end = start + length;
                for (int index = start; index < end; ++index) {
                    ch = chars[index];
                    if (ch == ']' && index + 2 < end && chars[index + 1] == ']' && chars[index + 2] == '>') {
                        _printer.printText("]]]]><![CDATA[>");
                        index += 2;
                        continue;
                    }
                    if (!XMLChar.isValid(ch)) {
                        // check if it is surrogate
                        if (++index < end) {
                            surrogates(ch, chars[index]);
                        } else {
                            fatalError("The character '" + (char) ch + "' is an invalid XML character");
                        }
                        continue;
                    } else {
                        if ((ch >= ' ' && _encodingInfo.isPrintable((char) ch) && ch != 0xF7) || ch == '\n' || ch == '\r' || ch == '\t') {
                            _printer.printText((char) ch);
                        } else {
                            // The character is not printable -- split CDATA section
                            _printer.printText("]]>&#x");
                            _printer.printText(Integer.toHexString(ch));
                            _printer.printText(";<![CDATA[");
                        }
                    }
                }
                _printer.setNextIndent(saveIndent);

            } else {

                int saveIndent;

                if (state.preserveSpace) {
                    // If preserving space then hold of indentation so no
                    // excessive spaces are printed at line breaks, escape
                    // the text content without replacing spaces and print
                    // the text breaking only at line breaks.
                    saveIndent = _printer.getNextIndent();
                    _printer.setNextIndent(0);
                    printText(chars, start, length, true, state.unescaped);
                    _printer.setNextIndent(saveIndent);
                } else {
                    printText(chars, start, length, false, state.unescaped);
                }
            }
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void ignorableWhitespace(char[] chars, int start, int length) throws SAXException {
        int i;

        try {
            content();

            // Print ignorable whitespaces only when indenting, after
            // all they are indentation. Cancel the indentation to
            // not indent twice.
            if (_indenting) {
                _printer.setThisIndent(0);
                for (i = start; length-- > 0; ++i)
                    _printer.printText(chars[i]);
            }
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public final void processingInstruction(String target, String code) throws SAXException {
        try {
            processingInstructionIO(target, code);
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void processingInstructionIO(String target, String code) throws IOException {
        int index;
        ElementState state;

        state = content();

        // Create the processing instruction textual representation.
        // Make sure we don't have '?>' inside either target or code.
        index = target.indexOf("?>");
        if (index >= 0)
            fStrBuffer.append("<?").append(target.substring(0, index));
        else
            fStrBuffer.append("<?").append(target);
        if (code != null) {
            fStrBuffer.append(' ');
            index = code.indexOf("?>");
            if (index >= 0)
                fStrBuffer.append(code.substring(0, index));
            else
                fStrBuffer.append(code);
        }
        fStrBuffer.append("?>");

        // If before the root element (or after it), do not print
        // the PI directly but place it in the pre-root vector.
        if (isDocumentState()) {
            if (_preRoot == null)
                _preRoot = new Vector();
            _preRoot.addElement(fStrBuffer.toString());
        } else {
            _printer.indent();
            printText(fStrBuffer.toString(), true, true);
            _printer.unindent();
            if (_indenting)
                state.afterElement = true;
        }

        fStrBuffer.setLength(0);
    }

    public void comment(char[] chars, int start, int length) throws SAXException {
        try {
            comment(new String(chars, start, length));
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void comment(String text) throws IOException {
        int index;
        ElementState state;

        if (_format.getOmitComments())
            return;

        state = content();
        // Create the processing comment textual representation.
        // Make sure we don't have '-->' inside the comment.
        index = text.indexOf("-->");
        if (index >= 0)
            fStrBuffer.append("<!--").append(text.substring(0, index)).append("-->");
        else
            fStrBuffer.append("<!--").append(text).append("-->");

        // If before the root element (or after it), do not print
        // the comment directly but place it in the pre-root vector.
        if (isDocumentState()) {
            if (_preRoot == null)
                _preRoot = new Vector();
            _preRoot.addElement(fStrBuffer.toString());
        } else {
            // Indent this element on a new line if the first
            // content of the parent element or immediately
            // following an element.
            if (_indenting && !state.preserveSpace)
                _printer.breakLine();
            _printer.indent();
            printText(fStrBuffer.toString(), true, true);
            _printer.unindent();
            if (_indenting)
                state.afterElement = true;
        }

        fStrBuffer.setLength(0);
        state.afterComment = true;
        state.afterElement = false;
    }

    public void startCDATA() {
        ElementState state;

        state = getElementState();
        state.doCData = true;
    }

    public void endCDATA() {
        ElementState state;

        state = getElementState();
        state.doCData = false;
    }

    public void startNonEscaping() {
        ElementState state;

        state = getElementState();
        state.unescaped = true;
    }

    public void endNonEscaping() {
        ElementState state;

        state = getElementState();
        state.unescaped = false;
    }

    public void startPreserving() {
        ElementState state;

        state = getElementState();
        state.preserveSpace = true;
    }

    public void endPreserving() {
        ElementState state;

        state = getElementState();
        state.preserveSpace = false;
    }

    /**
     * Called at the end of the document to wrap it up.
     * Will flush the output stream and throw an exception
     * if any I/O error occured while serializing.
     *
     * @throws SAXException An I/O exception occured during
     *  serializing
     */
    public void endDocument() throws SAXException {
        try {
            // Print all the elements accumulated outside of
            // the root element.
            serializePreRoot();
            // Flush the output, this is necessary for fStrBuffered output.
            _printer.flush();
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void startEntity(String name) {
        // ???
    }

    public void endEntity(String name) {
        // ???
    }

    public void setDocumentLocator(Locator locator) {
        // Nothing to do
    }

    //-----------------------------------------//
    // SAX content handler serializing methods //
    //-----------------------------------------//

    public void skippedEntity(String name) throws SAXException {
        try {
            endCDATA();
            content();
            _printer.printText('&');
            _printer.printText(name);
            _printer.printText(';');
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        if (_prefixes == null)
            _prefixes = new Hashtable();
        _prefixes.put(uri, prefix == null ? "" : prefix);
    }

    public void endPrefixMapping(String prefix) throws SAXException {
    }

    //------------------------------------------//
    // SAX DTD/Decl handler serializing methods //
    //------------------------------------------//

    public final void startDTD(String name, String publicId, String systemId) throws SAXException {
        try {
            _printer.enterDTD();
            _docTypePublicId = publicId;
            _docTypeSystemId = systemId;

        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void endDTD() {
        // Nothing to do here, all the magic occurs in startDocument(String).
    }

    public void elementDecl(String name, String model) throws SAXException {
        try {
            _printer.enterDTD();
            _printer.printText("<!ELEMENT ");
            _printer.printText(name);
            _printer.printText(' ');
            _printer.printText(model);
            _printer.printText('>');
            if (_indenting)
                _printer.breakLine();
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void attributeDecl(String eName, String aName, String type, String valueDefault, String value) throws SAXException {
        try {
            _printer.enterDTD();
            _printer.printText("<!ATTLIST ");
            _printer.printText(eName);
            _printer.printText(' ');
            _printer.printText(aName);
            _printer.printText(' ');
            _printer.printText(type);
            if (valueDefault != null) {
                _printer.printText(' ');
                _printer.printText(valueDefault);
            }
            if (value != null) {
                _printer.printText(" \"");
                printEscaped(value);
                _printer.printText('"');
            }
            _printer.printText('>');
            if (_indenting)
                _printer.breakLine();
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void internalEntityDecl(String name, String value) throws SAXException {
        try {
            _printer.enterDTD();
            _printer.printText("<!ENTITY ");
            _printer.printText(name);
            _printer.printText(" \"");
            printEscaped(value);
            _printer.printText("\">");
            if (_indenting)
                _printer.breakLine();
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException {
        try {
            _printer.enterDTD();
            unparsedEntityDecl(name, publicId, systemId, null);
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException {
        try {
            _printer.enterDTD();
            if (publicId == null) {
                _printer.printText("<!ENTITY ");
                _printer.printText(name);
                _printer.printText(" SYSTEM ");
                printDoctypeURL(systemId);
            } else {
                _printer.printText("<!ENTITY ");
                _printer.printText(name);
                _printer.printText(" PUBLIC ");
                printDoctypeURL(publicId);
                _printer.printText(' ');
                printDoctypeURL(systemId);
            }
            if (notationName != null) {
                _printer.printText(" NDATA ");
                _printer.printText(notationName);
            }
            _printer.printText('>');
            if (_indenting)
                _printer.breakLine();
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    public void notationDecl(String name, String publicId, String systemId) throws SAXException {
        try {
            _printer.enterDTD();
            if (publicId != null) {
                _printer.printText("<!NOTATION ");
                _printer.printText(name);
                _printer.printText(" PUBLIC ");
                printDoctypeURL(publicId);
                if (systemId != null) {
                    _printer.printText(' ');
                    printDoctypeURL(systemId);
                }
            } else {
                _printer.printText("<!NOTATION ");
                _printer.printText(name);
                _printer.printText(" SYSTEM ");
                printDoctypeURL(systemId);
            }
            _printer.printText('>');
            if (_indenting)
                _printer.breakLine();
        } catch (IOException except) {
            throw new SAXException(except);
        }
    }

    //------------------------------------------//
    // Generic node serializing methods methods //
    //------------------------------------------//

    /**
     * Serialize the DOM node. This method is shared across XML, HTML and XHTML
     * serializers and the differences are masked out in a separate {@link
     * #serializeElement}.
     *
     * @param node The node to serialize
     * @see #serializeElement
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    protected void serializeNode(Node node) throws IOException {
        fCurrentNode = node;

        // Based on the node type call the suitable SAX handler.
        // Only comments entities and documents which are not
        // handled by SAX are serialized directly.
        switch (node.getNodeType()) {
            case Node.TEXT_NODE: {
                String text;

                text = node.getNodeValue();
                if (text != null) {
                    if (fDOMFilter != null && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_TEXT) != 0) {
                        short code = fDOMFilter.acceptNode(node);
                        switch (code) {
                            case NodeFilter.FILTER_REJECT:
                            case NodeFilter.FILTER_SKIP: {
                                break;
                            }
                            default: {
                                characters(text);
                            }
                        }
                    } else if (!_indenting || getElementState().preserveSpace || (text.replace('\n', ' ').trim().length() != 0))
                        characters(text);

                }
                break;
            }

            case Node.CDATA_SECTION_NODE: {
                String text = node.getNodeValue();
                if ((features & DOMSerializerImpl.CDATA) != 0) {
                    if (text != null) {
                        if (fDOMFilter != null && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_CDATA_SECTION) != 0) {
                            short code = fDOMFilter.acceptNode(node);
                            switch (code) {
                                case NodeFilter.FILTER_REJECT:
                                case NodeFilter.FILTER_SKIP: {
                                    // skip the CDATA node
                                    return;
                                }
                                default: {
                                    //fall through..
                                }
                            }
                        }
                        startCDATA();
                        characters(text);
                        endCDATA();
                    }
                } else {
                    // transform into a text node
                    characters(text);
                }
                break;
            }
            case Node.COMMENT_NODE: {
                String text;

                if (!_format.getOmitComments()) {
                    text = node.getNodeValue();
                    if (text != null) {

                        if (fDOMFilter != null && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_COMMENT) != 0) {
                            short code = fDOMFilter.acceptNode(node);
                            switch (code) {
                                case NodeFilter.FILTER_REJECT:
                                case NodeFilter.FILTER_SKIP: {
                                    // skip the comment node
                                    return;
                                }
                                default: {
                                    // fall through
                                }
                            }
                        }
                        comment(text);
                    }
                }
                break;
            }

            case Node.ENTITY_REFERENCE_NODE: {
                Node child;

                endCDATA();
                content();

                if (((features & DOMSerializerImpl.ENTITIES) != 0) || (node.getFirstChild() == null)) {
                    if (fDOMFilter != null && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ENTITY_REFERENCE) != 0) {
                        short code = fDOMFilter.acceptNode(node);
                        switch (code) {
                            case NodeFilter.FILTER_REJECT: {
                                return; // remove the node
                            }
                            case NodeFilter.FILTER_SKIP: {
                                child = node.getFirstChild();
                                while (child != null) {
                                    serializeNode(child);
                                    child = child.getNextSibling();
                                }
                                return;
                            }

                            default: {
                                // fall through
                            }
                        }
                    }
                    checkUnboundNamespacePrefixedNode(node);

                    _printer.printText("&");
                    _printer.printText(node.getNodeName());
                    _printer.printText(";");
                } else {
                    child = node.getFirstChild();
                    while (child != null) {
                        serializeNode(child);
                        child = child.getNextSibling();
                    }
                }

                break;
            }

            case Node.PROCESSING_INSTRUCTION_NODE: {

                if (fDOMFilter != null && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_PROCESSING_INSTRUCTION) != 0) {
                    short code = fDOMFilter.acceptNode(node);
                    switch (code) {
                        case NodeFilter.FILTER_REJECT:
                        case NodeFilter.FILTER_SKIP: {
                            return; // skip this node
                        }
                        default: { // fall through
                        }
                    }
                }
                processingInstructionIO(node.getNodeName(), node.getNodeValue());
                break;
            }
            case Node.ELEMENT_NODE: {

                if (fDOMFilter != null && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ELEMENT) != 0) {
                    short code = fDOMFilter.acceptNode(node);
                    switch (code) {
                        case NodeFilter.FILTER_REJECT: {
                            return;
                        }
                        case NodeFilter.FILTER_SKIP: {
                            Node child = node.getFirstChild();
                            while (child != null) {
                                serializeNode(child);
                                child = child.getNextSibling();
                            }
                            return; // skip this node
                        }

                        default: { // fall through
                        }
                    }
                }
                serializeElement((Element) node);
                break;
            }
            case Node.DOCUMENT_NODE: {
                DocumentType docType;
                DOMImplementation domImpl;
                NamedNodeMap map;
                Entity entity;
                Notation notation;
                int i;

                serializeDocument();

                // If there is a document type, use the SAX events to
                // serialize it.
                docType = ((Document) node).getDoctype();
                if (docType != null) {
                    // DOM Level 2 (or higher)
                    domImpl = ((Document) node).getImplementation();
                    try {
                        String internal;

                        _printer.enterDTD();
                        _docTypePublicId = docType.getPublicId();
                        _docTypeSystemId = docType.getSystemId();
                        internal = docType.getInternalSubset();
                        if (internal != null && internal.length() > 0)
                            _printer.printText(internal);
                        endDTD();
                    }
                    // DOM Level 1 -- does implementation have methods?
                    catch (NoSuchMethodError nsme) {
                        Class docTypeClass = docType.getClass();

                        String docTypePublicId = null;
                        String docTypeSystemId = null;
                        try {
                            java.lang.reflect.Method getPublicId = docTypeClass.getMethod("getPublicId", (Class[]) null);
                            if (getPublicId.getReturnType().equals(String.class)) {
                                docTypePublicId = (String) getPublicId.invoke(docType, (Object[]) null);
                            }
                        } catch (Exception e) {
                            // ignore
                        }
                        try {
                            java.lang.reflect.Method getSystemId = docTypeClass.getMethod("getSystemId", (Class[]) null);
                            if (getSystemId.getReturnType().equals(String.class)) {
                                docTypeSystemId = (String) getSystemId.invoke(docType, (Object[]) null);
                            }
                        } catch (Exception e) {
                            // ignore
                        }
                        _printer.enterDTD();
                        _docTypePublicId = docTypePublicId;
                        _docTypeSystemId = docTypeSystemId;
                        endDTD();
                    }

                    serializeDTD(docType.getName());

                }
                _started = true;

                // !! Fall through
            }
            case Node.DOCUMENT_FRAGMENT_NODE: {
                Node child;

                // By definition this will happen if the node is a document,
                // document fragment, etc. Just serialize its contents. It will
                // work well for other nodes that we do not know how to serialize.
                child = node.getFirstChild();
                while (child != null) {
                    serializeNode(child);
                    child = child.getNextSibling();
                }
                break;
            }

            default:
                break;
        }
    }

    /* Serializes XML Declaration, according to 'xml-declaration' property.
     */
    protected void serializeDocument() throws IOException {
        int i;

        String dtd = _printer.leaveDTD();
        if (!_started) {

            if (!_format.getOmitXMLDeclaration()) {
                StringBuffer buffer;

                // Serialize the document declaration appreaing at the head
                // of very XML document (unless asked not to).
                buffer = new StringBuffer("<?xml version=\"");
                if (_format.getVersion() != null)
                    buffer.append(_format.getVersion());
                else
                    buffer.append("1.0");
                buffer.append('"');
                String format_encoding = _format.getEncoding();
                if (format_encoding != null) {
                    buffer.append(" encoding=\"");
                    buffer.append(format_encoding);
                    buffer.append('"');
                }
                if (_format.getStandalone() && _docTypeSystemId == null && _docTypePublicId == null)
                    buffer.append(" standalone=\"yes\"");
                buffer.append("?>");
                _printer.printText(buffer);
                _printer.breakLine();
            }
        }

        // Always serialize these, even if not te first root element.
        serializePreRoot();

    }

    /* Serializes DTD, if present.
     */
    protected void serializeDTD(String name) throws IOException {

        String dtd = _printer.leaveDTD();
        if (!_format.getOmitDocumentType()) {
            if (_docTypeSystemId != null) {
                // System identifier must be specified to print DOCTYPE.
                // If public identifier is specified print 'PUBLIC
                // <public> <system>', if not, print 'SYSTEM <system>'.
                _printer.printText("<!DOCTYPE ");
                _printer.printText(name);
                if (_docTypePublicId != null) {
                    _printer.printText(" PUBLIC ");
                    printDoctypeURL(_docTypePublicId);
                    if (_indenting) {
                        _printer.breakLine();
                        for (int i = 0; i < 18 + name.length(); ++i)
                            _printer.printText(" ");
                    } else
                        _printer.printText(" ");
                    printDoctypeURL(_docTypeSystemId);
                } else {
                    _printer.printText(" SYSTEM ");
                    printDoctypeURL(_docTypeSystemId);
                }

                // If we accumulated any DTD contents while printing.
                // this would be the place to print it.
                if (dtd != null && dtd.length() > 0) {
                    _printer.printText(" [");
                    printText(dtd, true, true);
                    _printer.printText(']');
                }

                _printer.printText(">");
                _printer.breakLine();
            } else if (dtd != null && dtd.length() > 0) {
                _printer.printText("<!DOCTYPE ");
                _printer.printText(name);
                _printer.printText(" [");
                printText(dtd, true, true);
                _printer.printText("]>");
                _printer.breakLine();
            }
        }
    }

    /**
     * Must be called by a method about to print any type of content.
     * If the element was just opened, the opening tag is closed and
     * will be matched to a closing tag. Returns the current element
     * state with <tt>empty</tt> and <tt>afterElement</tt> set to false.
     *
     * @return The current element state
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    protected ElementState content() throws IOException {
        ElementState state;

        state = getElementState();
        if (!isDocumentState()) {
            // Need to close CData section first
            if (state.inCData && !state.doCData) {
                _printer.printText("]]>");
                state.inCData = false;
            }
            // If this is the first content in the element,
            // change the state to not-empty and close the
            // opening element tag.
            if (state.empty) {
                _printer.printText('>');
                state.empty = false;
            }
            // Except for one content type, all of them
            // are not last element. That one content
            // type will take care of itself.
            state.afterElement = false;
            // Except for one content type, all of them
            // are not last comment. That one content
            // type will take care of itself.
            state.afterComment = false;
        }
        return state;
    }

    /**
     * Called to print the text contents in the prevailing element format.
     * Since this method is capable of printing text as CDATA, it is used
     * for that purpose as well. White space handling is determined by the
     * current element state. In addition, the output format can dictate
     * whether the text is printed as CDATA or unescaped.
     *
     * @param text The text to print
     * @param unescaped True is should print unescaped
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    protected void characters(String text) throws IOException {
        ElementState state;

        state = content();
        // Check if text should be print as CDATA section or unescaped
        // based on elements listed in the output format (the element
        // state) or whether we are inside a CDATA section or entity.

        if (state.inCData || state.doCData) {
            int index;
            int saveIndent;

            // Print a CDATA section. The text is not escaped, but ']]>'
            // appearing in the code must be identified and dealt with.
            // The contents of a text node is considered space preserving.
            if (!state.inCData) {
                _printer.printText("<![CDATA[");
                state.inCData = true;
            }
            saveIndent = _printer.getNextIndent();
            _printer.setNextIndent(0);
            printCDATAText(text);
            _printer.setNextIndent(saveIndent);

        } else {

            int saveIndent;

            if (state.preserveSpace) {
                // If preserving space then hold of indentation so no
                // excessive spaces are printed at line breaks, escape
                // the text content without replacing spaces and print
                // the text breaking only at line breaks.
                saveIndent = _printer.getNextIndent();
                _printer.setNextIndent(0);
                printText(text, true, state.unescaped);
                _printer.setNextIndent(saveIndent);
            } else {
                printText(text, false, state.unescaped);
            }
        }
    }

    /**
     * Returns the suitable entity reference for this character value,
     * or null if no such entity exists. Calling this method with <tt>'&amp;'</tt>
     * will return <tt>"&amp;amp;"</tt>.
     *
     * @param ch Character value
     * @return Character entity name, or null
     */
    protected abstract String getEntityRef(int ch);

    /**
     * Called to serializee the DOM element. The element is serialized based on
     * the serializer's method (XML, HTML, XHTML).
     *
     * @param elem The element to serialize
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    protected abstract void serializeElement(Element elem) throws IOException;

    /**
     * Comments and PIs cannot be serialized before the root element,
     * because the root element serializes the document type, which
     * generally comes first. Instead such PIs and comments are
     * accumulated inside a vector and serialized by calling this
     * method. Will be called when the root element is serialized
     * and when the document finished serializing.
     *
     * @throws IOException An I/O exception occured while
     *   serializing
     */
    protected void serializePreRoot() throws IOException {
        int i;

        if (_preRoot != null) {
            for (i = 0; i < _preRoot.size(); ++i) {
                printText((String) _preRoot.elementAt(i), true, true);
                if (_indenting)
                    _printer.breakLine();
            }
            _preRoot.removeAllElements();
        }
    }

    //---------------------------------------------//
    // Text pretty printing and formatting methods //
    //---------------------------------------------//

    protected void printCDATAText(String text) throws IOException {
        int length = text.length();
        char ch;

        for (int index = 0; index < length; ++index) {
            ch = text.charAt(index);
            if (ch == ']' && index + 2 < length && text.charAt(index + 1) == ']' && text.charAt(index + 2) == '>') { // check for ']]>'
                if (fDOMErrorHandler != null) {
                    // REVISIT: this means that if DOM Error handler is not registered we don't report any
                    // fatal errors and might serialize not wellformed document
                    if ((features & DOMSerializerImpl.SPLITCDATA) == 0) {
                        String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "EndingCDATA", null);
                        if ((features & DOMSerializerImpl.WELLFORMED) != 0) {
                            // issue fatal error
                            modifyDOMError(msg, DOMError.SEVERITY_FATAL_ERROR, "wf-invalid-character", fCurrentNode);
                            fDOMErrorHandler.handleError(fDOMError);
                            throw new LSException(LSException.SERIALIZE_ERR, msg);
                        } else {
                            // issue error
                            modifyDOMError(msg, DOMError.SEVERITY_ERROR, "cdata-section-not-splitted", fCurrentNode);
                            if (!fDOMErrorHandler.handleError(fDOMError)) {
                                throw new LSException(LSException.SERIALIZE_ERR, msg);
                            }
                        }
                    } else {
                        // issue warning
                        String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "SplittingCDATA", null);
                        modifyDOMError(msg, DOMError.SEVERITY_WARNING, null, fCurrentNode);
                        fDOMErrorHandler.handleError(fDOMError);
                    }
                }
                // split CDATA section
                _printer.printText("]]]]><![CDATA[>");
                index += 2;
                continue;
            }

            if (!XMLChar.isValid(ch)) {
                // check if it is surrogate
                if (++index < length) {
                    surrogates(ch, text.charAt(index));
                } else {
                    fatalError("The character '" + (char) ch + "' is an invalid XML character");
                }
                continue;
            } else {
                if ((ch >= ' ' && _encodingInfo.isPrintable((char) ch) && ch != 0xF7) || ch == '\n' || ch == '\r' || ch == '\t') {
                    _printer.printText((char) ch);
                } else {

                    // The character is not printable -- split CDATA section
                    _printer.printText("]]>&#x");
                    _printer.printText(Integer.toHexString(ch));
                    _printer.printText(";<![CDATA[");
                }
            }
        }
    }

    protected void surrogates(int high, int low) throws IOException {
        if (XMLChar.isHighSurrogate(high)) {
            if (!XMLChar.isLowSurrogate(low)) {
                //Invalid XML
                fatalError("The character '" + (char) low + "' is an invalid XML character");
            } else {
                int supplemental = XMLChar.supplemental((char) high, (char) low);
                if (!XMLChar.isValid(supplemental)) {
                    //Invalid XML
                    fatalError("The character '" + (char) supplemental + "' is an invalid XML character");
                } else {
                    if (content().inCData) {
                        _printer.printText("]]>&#x");
                        _printer.printText(Integer.toHexString(supplemental));
                        _printer.printText(";<![CDATA[");
                    } else {
                        printHex(supplemental);
                    }
                }
            }
        } else {
            fatalError("The character '" + (char) high + "' is an invalid XML character");
        }

    }

    /**
     * Called to print additional text with whitespace handling.
     * If spaces are preserved, the text is printed as if by calling
     * {@link #printText(String,boolean,boolean)} with a call to {@link Printer#breakLine}
     * for each new line. If spaces are not preserved, the text is
     * broken at space boundaries if longer than the line width;
     * Multiple spaces are printed as such, but spaces at beginning
     * of line are removed.
     *
     * @param text The text to print
     * @param preserveSpace Space preserving flag
     * @param unescaped Print unescaped
     */
    protected void printText(char[] chars, int start, int length, boolean preserveSpace, boolean unescaped) throws IOException {
        int index;
        char ch;

        if (preserveSpace) {
            // Preserving spaces: the text must print exactly as it is,
            // without breaking when spaces appear in the text and without
            // consolidating spaces. If a line terminator is used, a line
            // break will occur.
            while (length-- > 0) {
                ch = chars[start];
                ++start;
                if (ch == '\n' || ch == '\r' || unescaped)
                    _printer.printText(ch);
                else
                    printEscaped(ch);
            }
        } else {
            // Not preserving spaces: print one part at a time, and
            // use spaces between parts to break them into different
            // lines. Spaces at beginning of line will be stripped
            // by printing mechanism. Line terminator is treated
            // no different than other text part.
            while (length-- > 0) {
                ch = chars[start];
                ++start;
                if (ch == ' ' || ch == '\f' || ch == '\t' || ch == '\n' || ch == '\r')
                    _printer.printSpace();
                else if (unescaped)
                    _printer.printText(ch);
                else
                    printEscaped(ch);
            }
        }
    }

    protected void printText(String text, boolean preserveSpace, boolean unescaped) throws IOException {
        int index;
        char ch;

        if (preserveSpace) {
            // Preserving spaces: the text must print exactly as it is,
            // without breaking when spaces appear in the text and without
            // consolidating spaces. If a line terminator is used, a line
            // break will occur.
            for (index = 0; index < text.length(); ++index) {
                ch = text.charAt(index);
                if (ch == '\n' || ch == '\r' || unescaped)
                    _printer.printText(ch);
                else
                    printEscaped(ch);
            }
        } else {
            // Not preserving spaces: print one part at a time, and
            // use spaces between parts to break them into different
            // lines. Spaces at beginning of line will be stripped
            // by printing mechanism. Line terminator is treated
            // no different than other text part.
            for (index = 0; index < text.length(); ++index) {
                ch = text.charAt(index);
                if (ch == ' ' || ch == '\f' || ch == '\t' || ch == '\n' || ch == '\r')
                    _printer.printSpace();
                else if (unescaped)
                    _printer.printText(ch);
                else
                    printEscaped(ch);
            }
        }
    }

    /**
     * Print a document type public or system identifier URL.
     * Encapsulates the URL in double quotes, escapes non-printing
     * characters and print it equivalent to {@link #printText}.
     *
     * @param url The document type url to print
     */
    protected void printDoctypeURL(String url) throws IOException {
        int i;

        _printer.printText('"');
        for (i = 0; i < url.length(); ++i) {
            if (url.charAt(i) == '"' || url.charAt(i) < 0x20 || url.charAt(i) > 0x7F) {
                _printer.printText('%');
                _printer.printText(Integer.toHexString(url.charAt(i)));
            } else
                _printer.printText(url.charAt(i));
        }
        _printer.printText('"');
    }

    protected void printEscaped(int ch) throws IOException {
        String charRef;
        // If there is a suitable entity reference for this
        // character, print it. The list of available entity
        // references is almost but not identical between
        // XML and HTML.
        charRef = getEntityRef(ch);
        if (charRef != null) {
            _printer.printText('&');
            _printer.printText(charRef);
            _printer.printText(';');
        } else if ((ch >= ' ' && _encodingInfo.isPrintable((char) ch) && ch != 0xF7) || ch == '\n' || ch == '\r' || ch == '\t') {
            // Non printables are below ASCII space but not tab or line
            // terminator, ASCII delete, or above a certain Unicode threshold.
            if (ch < 0x10000) {
                _printer.printText((char) ch);
            } else {
                _printer.printText((char) (((ch - 0x10000) >> 10) + 0xd800));
                _printer.printText((char) (((ch - 0x10000) & 0x3ff) + 0xdc00));
            }
        } else {
            printHex(ch);
        }
    }

    /**
     * Escapes chars
     */
    final void printHex(int ch) throws IOException {
        _printer.printText("&#x");
        _printer.printText(Integer.toHexString(ch));
        _printer.printText(';');

    }

    /**
     * Escapes a string so it may be printed as text content or attribute
     * value. Non printable characters are escaped using character references.
     * Where the format specifies a deault entity reference, that reference
     * is used (e.g. <tt>&amp;lt;</tt>).
     *
     * @param source The string to escape
     */
    protected void printEscaped(String source) throws IOException {
        for (int i = 0; i < source.length(); ++i) {
            int ch = source.charAt(i);
            if ((ch & 0xfc00) == 0xd800 && i + 1 < source.length()) {
                int lowch = source.charAt(i + 1);
                if ((lowch & 0xfc00) == 0xdc00) {
                    ch = 0x10000 + ((ch - 0xd800) << 10) + lowch - 0xdc00;
                    i++;
                }
            }
            printEscaped(ch);
        }
    }

    //--------------------------------//
    // Element state handling methods //
    //--------------------------------//

    /**
     * Return the state of the current element.
     *
     * @return Current element state
     */
    protected ElementState getElementState() {
        return _elementStates[_elementStateCount];
    }

    /**
     * Enter a new element state for the specified element.
     * Tag name and space preserving is specified, element
     * state is initially empty.
     *
     * @return Current element state, or null
     */
    protected ElementState enterElementState(String namespaceURI, String localName, String rawName, boolean preserveSpace) {
        ElementState state;

        if (_elementStateCount + 1 == _elementStates.length) {
            ElementState[] newStates;

            // Need to create a larger array of states. This does not happen
            // often, unless the document is really deep.
            newStates = new ElementState[_elementStates.length + 10];
            for (int i = 0; i < _elementStates.length; ++i)
                newStates[i] = _elementStates[i];
            for (int i = _elementStates.length; i < newStates.length; ++i)
                newStates[i] = new ElementState();
            _elementStates = newStates;
        }

        ++_elementStateCount;
        state = _elementStates[_elementStateCount];
        state.namespaceURI = namespaceURI;
        state.localName = localName;
        state.rawName = rawName;
        state.preserveSpace = preserveSpace;
        state.empty = true;
        state.afterElement = false;
        state.afterComment = false;
        state.doCData = state.inCData = false;
        state.unescaped = false;
        state.prefixes = _prefixes;

        _prefixes = null;
        return state;
    }

    /**
     * Leave the current element state and return to the
     * state of the parent element. If this was the root
     * element, return to the state of the document.
     *
     * @return Previous element state
     */
    protected ElementState leaveElementState() {
        if (_elementStateCount > 0) {
            /*Corrected by David Blondeau (blondeau@intalio.com)*/
            _prefixes = null;
            //_prefixes = _elementStates[ _elementStateCount ].prefixes;
            --_elementStateCount;
            return _elementStates[_elementStateCount];
        } else {
            String msg = DOMMessageFormatter.formatMessage(DOMMessageFormatter.SERIALIZER_DOMAIN, "Internal", null);
            throw new IllegalStateException(msg);
        }
    }

    /**
     * Returns true if in the state of the document.
     * Returns true before entering any element and after
     * leaving the root element.
     *
     * @return True if in the state of the document
     */
    protected boolean isDocumentState() {
        return _elementStateCount == 0;
    }

    /**
     * Returns the namespace prefix for the specified URI.
     * If the URI has been mapped to a prefix, returns the
     * prefix, otherwise returns null.
     *
     * @param namespaceURI The namespace URI
     * @return The namespace prefix if known, or null
     */
    protected String getPrefix(String namespaceURI) {
        String prefix;

        if (_prefixes != null) {
            prefix = (String) _prefixes.get(namespaceURI);
            if (prefix != null)
                return prefix;
        }
        if (_elementStateCount == 0)
            return null;
        else {
            for (int i = _elementStateCount; i > 0; --i) {
                if (_elementStates[i].prefixes != null) {
                    prefix = (String) _elementStates[i].prefixes.get(namespaceURI);
                    if (prefix != null)
                        return prefix;
                }
            }
        }
        return null;
    }

    /**
     * The method modifies global DOM error object
     *
     * @param message
     * @param severity
     * @param type
     * @return a DOMError
     */
    protected DOMError modifyDOMError(String message, short severity, String type, Node node) {
        fDOMError.reset();
        fDOMError.fMessage = message;
        fDOMError.fType = type;
        fDOMError.fSeverity = severity;
        fDOMError.fLocator = new DOMLocatorImpl(-1, -1, -1, node, null);
        return fDOMError;

    }

    protected void fatalError(String message) throws IOException {
        if (fDOMErrorHandler != null) {
            modifyDOMError(message, DOMError.SEVERITY_FATAL_ERROR, null, fCurrentNode);
            fDOMErrorHandler.handleError(fDOMError);
        } else {
            throw new IOException(message);
        }
    }

    /**
     * DOM level 3:
     * Check a node to determine if it contains unbound namespace prefixes.
     *
     * @param node The node to check for unbound namespace prefices
     */
    protected void checkUnboundNamespacePrefixedNode(Node node) throws IOException {

    }
}
